﻿// Copyright (c) 2008 Madgex
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 
// OAuth.net uses the Common Service Locator interface, released under the MS-PL
// license. See "CommonServiceLocator License.txt" in the Licenses folder.
// 
// The examples and test cases use the Windsor Container from the Castle Project
// and Common Service Locator Windsor adaptor, released under the Apache License,
// Version 2.0. See "Castle Project License.txt" in the Licenses folder.
// 
// XRDS-Simple.net uses the HTMLAgility Pack. See "HTML Agility Pack License.txt"
// in the Licenses folder.
//
// Authors: Bruce Boughton, Chris Adams
// Website: http://lab.madgex.com/oauth-net/
// Email:   oauth-dot-net@madgex.com

using System;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using OAuth.Net.Common;

namespace OAuth.Net.Consumer
{
    /// <summary>
    /// Makes a request to an OAuth protected resource.
    /// </summary>
    /// <remarks>
    /// <para>
    /// To create an <see cref="OAuthRequest"/>, use the Create 
    /// methods. You should pass a configured <see cref="OAuthService"/> which
    /// encapsulated the protocol configuration details.
    /// </para>
    /// 
    /// <para>
    /// When constructing an <see cref="OAuthRequest"/>, you should pass the 
    /// request and/or access tokens if you have already performed user 
    /// authorization.
    /// </para>
    /// 
    /// <para>
    /// If a valid access token is supplied, this token will be used to request
    /// the protected resource when GetResource is called and the
    /// returned <see cref="OAuthResponse"/> should contain the resource 
    /// representation as an <see cref="OAuthResource"/>. If not, 
    /// <see cref="OAuthResponse.HasProtectedResource"/> will be <c>false</c>.
    /// </para>
    /// 
    /// <para>
    /// If no access token is supplied, but an authorized request token is 
    /// passed, calling GetResource will first request an access
    /// token. If successful, the protected resource request will then proceed.
    /// The returned <see cref="OAuthResponse"/> will contain the access token
    /// obtained in its <see cref="OAuthResponse.Token"/> property and the
    /// resource representation as a <see cref="OAuthResource"/>. 
    /// <see cref="OAuthResponse.HasProtectedResource"/> will be <c>true</c>.
    /// </para>
    /// 
    /// <para>
    /// If no tokens are supplied at all, or if the request token supplied is
    /// not authenticated (and no access token is supplied), then the user will
    /// be directed to perform authorization of the request. The 
    /// <see cref="OAuthResponse.HasProtectedResource"/> property will be 
    /// <c>false</c>. If a request token has been obtained, it will be stored 
    /// in the <see cref="OAuthResponse.Token"/> property. You should direct the
    /// user to the authorization URL as generated by 
    /// OAuthService.BuildAuthorizationUrl (passing in the request 
    /// token and optionally a callback URL and/or additional parameters). Once
    /// the user has completed the authorization step, you should re-submit the
    /// <see cref="OAuthRequest"/> with the now authenticated response token
    /// and proceed as above.
    /// </para>
    /// 
    /// <para>
    /// If the service provider supports the Problem Reporting extension and
    /// an error occurs, a report will be thrown as a 
    /// <see cref="OAuthRequestException"/>. You should use this to inform your
    /// response to this error.
    /// </para>
    /// 
    /// <example>
    /// The following example is taken from the article, 
    /// <see href="http://lab.madgex.com/oauth-net/gettingatarted01.aspx/">
    /// Getting Started: Building a Fire Eagle consumer with OAuth.net</see>.
    /// 
    /// <code>
    /// // Find the user's location
    /// var request = OAuthRequest.Create(
    ///     new Uri("https://fireeagle.yahooapis.com/api/0.1/user"),
    ///     FireEagle.GetService,
    ///     context.Session["request_token"] as IToken,
    ///     context.Session["access_token"] as IToken);
    /// OAuthResponse response = request.GetResource();
    /// 
    /// if (response.HasProtectedResource)
    /// {
    ///     // Store the access token
    ///     context.Session["access_token"] = response.Token;
    ///  
    ///  
    ///     // Load the response XML
    ///     XmlDocument responseXml = new XmlDocument();
    ///     responseXml.Load(response.ProtectedResource.GetResponseStream());
    ///  
    ///  
    ///     // Check the response status
    ///     if (responseXml.SelectSingleNode("rsp/@stat").Value == "ok")
    ///  
    ///         return Location.Parse(responseXml.SelectSingleNode(
    ///             "rsp/user/location-hierarchy/location[@best-guess='true']"));
    ///     else
    ///         return null;
    /// }
    /// else
    /// {
    ///     // Authorization is required
    ///     context.Session["request_token"] = response.Token;
    ///     throw new AuthorizationRequiredException()
    ///     {
    ///         AuthorizationUri = FireEagle.GetService.BuildAuthorizationUrl(
    ///             response.Token, 
    ///             callback)
    ///     };
    /// }
    /// </code>
    /// </example>
    /// 
    /// </remarks>
    public class OAuthRequest
    {
        /// <summary>
        /// 
        /// </summary>
        private readonly IRequestStateStore _stateStore;

        /// <summary>
        /// 
        /// </summary>
        private readonly RequestState _state;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="resourceEndPoint"></param>
        /// <param name="settings"></param>
        /// <param name="verifier"></param>
        /// <param name="state"></param>
        protected OAuthRequest(EndPoint resourceEndPoint, OAuthService settings,
            string verifier, RequestState state)
        {
            ResourceEndPoint = resourceEndPoint;
            Service = settings;
            RequestTokenVerifier = verifier;

            _state = state;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="resourceEndPoint"></param>
        /// <param name="settings"></param>
        /// <param name="verifier"></param>
        /// <param name="stateStore"></param>
        /// <param name="stateKey"></param>
        protected OAuthRequest(EndPoint resourceEndPoint, OAuthService settings, string verifier,
            IRequestStateStore stateStore, RequestStateKey stateKey)
        {
            ResourceEndPoint = resourceEndPoint;
            Service = settings;
            RequestTokenVerifier = verifier;

            _stateStore = stateStore;
            _state = stateStore.Get(stateKey);
        }

        /// <summary>
        /// This event is fired before the request to get a request token is created. You may
        /// modify the request Uri and HTTP method, as well as add additional request 
        /// parameters to be sent with the request in the query string or post body.
        /// </summary>
        public event EventHandler<PreRequestTokenRequestEventArgs> BeforeGetRequestToken;

        /// <summary>
        /// This event is fired after the request token has been received. If an 
        /// exception occurs receiving the response, this event will not fire.
        /// </summary>
        public event EventHandler<RequestTokenReceivedEventArgs> RequestTokenReceived;

        /// <summary>
        /// This event is fired before the request to get an access token is created. You may
        /// modify the request Uri and HTTP method.
        /// </summary>
        public event EventHandler<PreAccessTokenRequestEventArgs> BeforeGetAccessToken;

        /// <summary>
        /// This event is fired after the access token has been received. If an 
        /// exception occurs receiving the response, this event will not fire.
        /// </summary>
        public event EventHandler<AccessTokenReceivedEventArgs> AccessTokenReceived;

        /// <summary>
        /// This event is fired before the request to get the protected resource is created.
        /// You may modify the request Uri and HTTP method, as well as add additional request 
        /// parameters to be sent with the request in the query string or post body.
        /// </summary>
        public event EventHandler<PreProtectedResourceRequestEventArgs> BeforeGetProtectedResource;

        /// <summary>
        /// This event is fired before the request to get the public resource is created.
        /// You may modify the request Uri and HTTP method, as well as add additional request 
        /// parameters to be sent with the request in the query string or post body.
        /// </summary>
        public event EventHandler<PrePublicResourceRequestEventArgs> BeforeGetPublicResource;

        /// <summary>
        ///     
        /// </summary>
        public event EventHandler<PreSendHttpWebRequestEventArgs> BeforeSendHttpWebRequest;

        /// <summary>
        /// The EndPoint for the protected resource
        /// </summary>
        public EndPoint ResourceEndPoint { get; private set; }

        /// <summary>
        /// The protected resource Uri
        /// </summary>
        public Uri ResourceUri
        {
            get
            {
                return ResourceEndPoint.Uri;
            }
        }

        /// <summary>
        /// The OAuth service
        /// </summary>
        public OAuthService Service { get; private set; }

        /// <summary>
        /// The request token
        /// </summary>
        public IToken RequestToken
        {
            get
            {
                return _state.RequestToken;
            }

            private set
            {
                _state.RequestToken = value;

                if (_stateStore != null)
                {
                    _stateStore.Store(_state);
                }
            }
        }

        /// <summary>
        /// The access token
        /// </summary>
        public IToken AccessToken
        {
            get
            {
                return _state.AccessToken;
            }

            private set
            {
                _state.AccessToken = value;

                if (_stateStore != null)
                    _stateStore.Store(_state);
            }
        }

        /// <summary>
        /// The URL that the user is redirected to by the Service Provider.  If no Uri is defined 
        /// the request is defined as out-of-band and the call back has been defined elsewhere or will
        /// be performed manually.
        /// <remarks>This value can be dynamically updated during the request by handling the OnBeforeGetRequestToken 
        /// event in OauthRequest</remarks>
        /// </summary>
        public Uri CallbackUrl { get; private set; }

        /// <summary>
        /// The verifier parameter provided by the service provider 
        /// that needs to be returned on the request for the access token.
        /// </summary>
        public string RequestTokenVerifier { get; private set; }

        /// <summary>
        /// This delegate, if not <c>null</c>, is called before requesting the
        /// access token to collect the verifier issued by the service provider.
        /// </summary>
        /// <remarks>
        /// <para>
        /// It is not required to use this mechanism. Instead, you can provide
        /// the verifier to OAuthRequest.Create.
        /// </para>
        /// 
        /// <para>
        /// If you supply a handler, it should attempt to provide the verifier
        /// in an application-specific manner when raised.
        /// </para>
        /// </remarks>
        public EventHandler<RequestTokenParseArgs> RequestTokenParseHandler { get; set; }

        /// <summary>
        /// This delegate, if not <c>null</c>, is called before requesting the
        /// access token to collect the verifier issued by the service provider.
        /// </summary>
        /// <remarks>
        /// <para>
        /// It is not required to use this mechanism. Instead, you can provide
        /// the verifier to OAuthRequest.Create.
        /// </para>
        /// 
        /// <para>
        /// If you supply a handler, it should attempt to provide the verifier
        /// in an application-specific manner when raised.
        /// </para>
        /// </remarks>
        public EventHandler<AuthorizationVerificationEventArgs> VerificationHandler { get; set; }

        /// <summary>
        /// This delegate, if not <c>null</c>, is called when the request token
        /// requires authorization.
        /// </summary>
        /// <remarks>
        /// <para>
        /// It is not required to use this mechanism. Instead, you can inspect
        /// <see cref="OAuthResponse.HasProtectedResource"/> and direct the
        /// user to authorize if <c>false</c>.
        /// </para>
        /// 
        /// <para>
        /// If you supply a handler, it should direct the user to authorize in
        /// an application-specific manner. If this can be done without 
        /// aborting the current thread, 
        /// <see cref="AuthorizationEventArgs.ContinueOnReturn"/> should be set
        /// to <c>true</c> to indicate that the workflow can continue without
        /// re-submitting the <see cref="OAuthRequest"/>.
        /// </para>
        /// 
        /// <para>
        /// In either mechanism, the authorization URI is built using
        /// OAuthService.BuildAuthorizationUrl. If additional parameters are
        /// supplied, these will be included in the authorization URI 
        /// generated.
        /// </para>
        /// </remarks>
        public EventHandler<AuthorizationEventArgs> AuthorizationHandler { get; set; }

        /// <summary>
        /// Creates a new OAuth protected request, using the supplied end user ID
        /// in combination with the service to create a state key to load and 
        /// store request state such as tokens.
        /// </summary>
        /// <param name="resourceEndPoint">Protected resource End Point</param>
        /// <param name="settings">Service settings</param>
        /// <param name="callbackUri">Callback URI</param>
        /// <param name="endUserId">End user ID</param>
        /// <param name="verifier">Verifier</param>
        /// <returns>An OAuth protected request for the protected resource,
        /// initialised using the configured state store</returns>
        public static OAuthRequest Create(EndPoint resourceEndPoint, OAuthService settings,
            Uri callbackUri, string verifier, string endUserId)
        {
            var stateStore = settings.ComponentLocator.GetInstance<IRequestStateStore>();

            var request =
                new OAuthRequest(resourceEndPoint, settings,
                    verifier, stateStore, new RequestStateKey(settings, endUserId))
                {
                    CallbackUrl = callbackUri
                };

            return request;
        }

        /// <summary>
        /// Creates a new OAuth protected requests.
        /// </summary>
        /// <remarks>
        /// Since neither a request token nor an access token is supplied,
        /// the user will have to authorize this request.
        /// </remarks>
        /// <param name="resourceEndPoint">Protected resource End Point</param>
        /// <param name="settings">Service settings</param>
        /// <returns>An OAuth protected request for the protected resource</returns>
        public static OAuthRequest Create(EndPoint resourceEndPoint, OAuthService settings)
        {
            return OAuthRequest.Create(resourceEndPoint, settings, null as IToken, null as IToken);
        }

        /// <summary>
        /// Creates a new OAuth protected request, initialised with a previously
        /// retrieved request token. This token may or may not have been authorized.
        /// </summary>
        /// <remarks>
        /// If the request token supplied has not been authorized, the user will
        /// have to be directed to authorize it before the request can proceed.
        /// </remarks>
        /// <param name="resourceEndPoint">Protected resource End Point</param>
        /// <param name="settings">Service settings</param>
        /// <param name="requestToken">Request token</param>
        /// <returns>An OAuth protected request for the protected resource</returns>
        public static OAuthRequest Create(EndPoint resourceEndPoint,
            OAuthService settings, IToken requestToken)
        {
            return OAuthRequest.Create(resourceEndPoint, settings, requestToken, null);
        }

        /// <summary>
        /// Creates a new OAuth protected request, initialised with previously
        /// retrieved request and access tokens. 
        /// </summary>
        /// <remarks>
        /// If the access token is valid, the user should not have to intervene
        /// to authorize the request and the protected resource should be
        /// fetched immediately.
        /// </remarks>
        /// <param name="resourceEndPoint">Protected resource End Point</param>
        /// <param name="settings">Service settings</param>
        /// <param name="requestToken">Request token</param>
        /// <param name="accessToken">Access token</param>
        /// <returns>An OAuth protected request for the protected resource,
        /// initialised with the request token and access token</returns>
        public static OAuthRequest Create(EndPoint resourceEndPoint, OAuthService settings,
            IToken requestToken, IToken accessToken)
        {
            return OAuthRequest.Create(resourceEndPoint,
                settings, null, requestToken, null, accessToken);
        }

        /// <summary>
        /// Creates a new OAuth protected request, initialised with previously
        /// retrieved request and access tokens, the specified callback  
        /// </summary>
        /// <remarks>
        /// If the access token is valid, the user should not have to intervene
        /// to authorize the request and the protected resource should be
        /// fetched immediately.
        /// </remarks>
        /// <param name="resourceEndPoint">Protected resource End Point</param>
        /// <param name="settings">Service settings</param>
        /// <param name="callbackUri">Callback uri</param>
        /// <param name="requestToken">Request token</param>
        /// <param name="accessToken">Access token</param>
        /// <returns>An OAuth protected request for the protected resource,
        /// initialised with the request token and access token</returns>
        public static OAuthRequest Create(EndPoint resourceEndPoint, OAuthService settings,
            Uri callbackUri, IToken requestToken, IToken accessToken)
        {
            return OAuthRequest.Create(resourceEndPoint, settings,
                callbackUri, requestToken, null, accessToken);
        }

        /// <summary>
        /// Creates a new OAuth protected request, initialised with previously
        /// retrieved request and access tokens, the specified callback  
        /// </summary>
        /// <remarks>
        /// If the access token is valid, the user should not have to intervene
        /// to authorize the request and the protected resource should be
        /// fetched immediately.
        /// </remarks>
        /// <param name="resourceEndPoint">Protected resource End Point</param>
        /// <param name="settings">Service settings</param>
        /// <param name="callbackUri">Callback uri</param>
        /// <param name="requestToken">Request token</param>
        /// <param name="verifier">Verifier</param>
        /// <param name="accessToken">Access token</param>
        /// <returns>An OAuth protected request for the protected resource,
        /// initialised with the request token and access token</returns>
        public static OAuthRequest Create(EndPoint resourceEndPoint,
            OAuthService settings, Uri callbackUri,
            IToken requestToken, string verifier, IToken accessToken)
        {
            var state = new RequestState(new RequestStateKey(settings, null))
            {
                RequestToken = requestToken,
                AccessToken = accessToken
            };

            var request = new OAuthRequest(resourceEndPoint, settings, verifier, state)
            {
                CallbackUrl = callbackUri
            };
            return request;
        }

        /// <summary>
        /// Creates a new OAuth protected request, using the supplied end user ID
        /// in combination with the service to create a state key to load and 
        /// store request state such as tokens. 
        /// </summary>
        /// <param name="resourceEndPoint">Protected resource End Point</param>
        /// <param name="settings">Service settings</param>
        /// <param name="endUserId">End user ID</param>
        /// <returns>An OAuth protected request for the protected resource,
        /// initialized using the configured state store</returns>
        public static OAuthRequest Create(EndPoint resourceEndPoint,
            OAuthService settings, string endUserId)
        {
            return OAuthRequest.Create(resourceEndPoint, settings, null, null, endUserId);
        }

        /// <summary>
        /// Creates a new OAuth protected request, using the supplied end user ID
        /// in combination with the service to create a state key to load and 
        /// store request state such as tokens.
        /// </summary>
        /// <param name="resourceEndPoint">Protected resource End Point</param>
        /// <param name="settings">Service settings</param>
        /// <param name="callbackUri">Callback URI</param>
        /// <param name="endUserId">End user ID</param>
        /// <returns>An OAuth protected request for the protected resource,
        /// initialized using the configured state store</returns>
        public static OAuthRequest Create(EndPoint resourceEndPoint, OAuthService settings,
            Uri callbackUri, string endUserId)
        {
            return OAuthRequest.Create(resourceEndPoint, settings, callbackUri, null, endUserId);
        }

        /// <exception cref="OAuth.Net.Common.OAuthRequestException">
        /// <list>
        /// <item>If the server responds with an OAuthRequestException</item>
        /// <item>If the server's responds unexpectedly</item>
        /// <item>If the requests to the server cannot be signed</item>
        /// </list>
        /// </exception>
        protected virtual HttpWebRequest PrepareProtectedResourceRequest
            (NameValueCollection parameters, string contentType,
            Stream bodyStream, out string postData)
        {
            postData = string.Empty;
            if (AccessToken == null)
            {
                if (RequestToken == null)
                {
                    DoGetRequestToken();  // have not request token so get one
                    if (RequestToken == null)
                    {
                        throw new InvalidOperationException("Request token was not received.");
                    }

                    // Get the authorization handler to authorize the request token
                    // Halt processing if the authorization handler is out-of-band
                    if (!DoAuthorizeRequestToken())
                    {
                        return null;
                    }
                }

                if (string.IsNullOrEmpty(RequestTokenVerifier))
                {
                    // try to collect the verifier
                    DoCollectVerifier();
                }

                if (string.IsNullOrEmpty(RequestTokenVerifier))
                {
                    return null;
                }

                // get the access token - this will return false if the verifier is not provided
                // the implementation needs to get the user to re-authenticate.
                if (!DoGetAccessToken())
                {
                    return null;
                }
            }

            if (AccessToken == null)
            {
                throw new InvalidOperationException("Access token was not received.");
            }

            return DoPrepareProtectedResourceRequest(parameters, contentType, bodyStream, out postData);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="contentType"></param>
        /// <param name="bodyStream"></param>
        /// <returns></returns>
        protected virtual HttpWebRequest DoPrepareProtectedResourceRequest(
            NameValueCollection parameters, string contentType, Stream bodyStream, out string postData)
        {
            // Fire the OnBeforeGetProtectedResource event
            var preArgs = new PreProtectedResourceRequestEventArgs(
                ResourceUri, ResourceEndPoint.HttpMethod,
                parameters ?? new NameValueCollection(),
                RequestToken, AccessToken);

            if (BeforeGetProtectedResource != null)
            {
                BeforeGetProtectedResource(this, preArgs);
            }

            var oauthParams = CreateOAuthParameters(preArgs.AdditionalParameters);

            SignParameters(preArgs.RequestUri, preArgs.HttpMethod, oauthParams, AccessToken);
            return CreateRequest(preArgs.RequestUri, oauthParams,
                preArgs.HttpMethod, contentType, bodyStream, out postData);
        }

        /// <exception cref="OAuth.Net.Common.OAuthRequestException">
        /// <list>
        /// <item>If the server responds with an OAuthRequestException</item>
        /// <item>If the server's responds unexpectedly</item>
        /// <item>If the requests to the server cannot be signed</item>
        /// </list>
        /// </exception>
        protected virtual HttpWebRequest PreparePublicResourceRequest
            (NameValueCollection parameters, string contentType, Stream bodyStream, out string postData)
        {
            var preArgs = new PrePublicResourceRequestEventArgs(
                            ResourceUri,
                            ResourceEndPoint.HttpMethod,
                            parameters ?? new NameValueCollection());

            if (BeforeGetPublicResource != null)                // fire the OnBeforeGetRequestToken event
            {
                BeforeGetPublicResource(this, preArgs);
            }

            var oauthParams = CreateOAuthParameters(preArgs.AdditionalParameters);
            SignParameters(preArgs.RequestUri, preArgs.HttpMethod, oauthParams, null);

            // create and sign the request
            return CreateRequest(preArgs.RequestUri, oauthParams, preArgs.HttpMethod,
                contentType, bodyStream, out postData);
        }

        /// <summary>
        /// 
        /// </summary>
        protected virtual void DoGetRequestToken()
        {
            var httpRequest = CreateRequestTokenRequest();

            HttpWebResponse httpResponse = null;
            OAuthParameters responseParameters = null;

            try     // get the service provider response
            {
                if (BeforeSendHttpWebRequest != null)
                {
                    BeforeSendHttpWebRequest(this, new PreSendHttpWebRequestEventArgs(httpRequest));
                }

                httpResponse = (HttpWebResponse)httpRequest.GetResponse();

                if (RequestTokenParseHandler != null)
                {
                    responseParameters = RequestTokenParseHandler(this, httpResponse);
                }

                // Parse the parameters and re-throw any OAuthRequestException from the service provider
                responseParameters = OAuthParameters.Parse(httpResponse);
                OAuthRequestException.TryRethrow(responseParameters);
            }
            catch (WebException e)
            {
                // Parse the parameters and re-throw any OAuthRequestException from the service provider
                responseParameters = OAuthParameters.Parse(e.Response as HttpWebResponse);
                OAuthRequestException.TryRethrow(responseParameters);

                // If no OAuthRequestException, rethrow the WebException
                throw;
            }

            // Store the request token
            RequestToken = new OAuthToken(
                TokenType.Request,
                responseParameters.Token,
                responseParameters.TokenSecret,
                Service.Consumer);

            // Fire the OnReceiveRequestToken event
            var responseArgs = new RequestTokenReceivedEventArgs(
                RequestToken,
                responseParameters.AdditionalParameters);

            if (RequestTokenReceived != null)
            {
                RequestTokenReceived(this, responseArgs);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public HttpWebRequest CreateRequestTokenRequest()
        {
            // fire the OnBeforeGetRequestToken event
            var args = new PreRequestTokenRequestEventArgs(
                            Service.RequestTokenUrl,
                            Service.RequestTokenEndPoint.HttpMethod,
                            CallbackUrl,
                            new NameValueCollection());

            if (BeforeGetRequestToken != null)
            {
                BeforeGetRequestToken(this, args);
            }

            var oauthParams = CreateOAuthParameters(args.AdditionalParameters);
            oauthParams.Callback = args.CallbackUrl == null ?
                Constants.OAuthOutOfBandCallback : args.CallbackUrl.AbsoluteUri;

            SignParameters(args.RequestUri, args.HttpMethod, oauthParams, null);

            // create and sign the request
            return CreateRequest(
                args.RequestUri, oauthParams, args.HttpMethod,
                string.Compare(args.HttpMethod, "POST", false) == 0 ?
                Constants.HttpPostUrlEncodedContentType : String.Empty,
                null);
        }

        /// <summary>
        /// Raises the AuthorizationEventArgs that allows a Consumer to determine 
        /// if the request should stop and return the RequestToken or 
        /// continue and request the access token.  This allow the a Consumer desktop 
        /// app to sleep the thread whilst the consumer goes elsewhere to perform
        /// the authorization.
        /// </summary>
        /// <returns></returns>
        protected virtual bool DoAuthorizeRequestToken()
        {
            if (RequestToken == null)
            {
                throw new InvalidOperationException("Request token must be present");
            }

            // Invoke the authorization handler
            var authArgs = new AuthorizationEventArgs(RequestToken);

            if (AuthorizationHandler != null)
            {
                AuthorizationHandler(this, authArgs);
            }

            return authArgs.ContinueOnReturn;
        }

        /// <summary>
        /// Raises the AuthorizationVerificationEventArgs that allows a Consumer to provide
        /// the verifier received after authorization if it has it.
        /// </summary>
        /// <returns></returns>
        protected virtual void DoCollectVerifier()
        {
            // Invoke the authorization handler
            var verificationArgs = new AuthorizationVerificationEventArgs();

            if (VerificationHandler != null)
            {
                VerificationHandler(this, verificationArgs);
            }

            // store the verifier if it has been specified
            if (!string.IsNullOrEmpty(verificationArgs.Verifier))
            {
                RequestTokenVerifier = verificationArgs.Verifier;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        protected virtual bool DoGetAccessToken()
        {
            // We don't have a verifier so something has gone wrong in the process.                
            if (string.IsNullOrEmpty(RequestTokenVerifier))
            {
                return false;
            }

            var request = CreateAccessTokenRequest();

            HttpWebResponse response = null;
            OAuthParameters responseParameters = null;

            // Get the service provider response
            try
            {
                if (BeforeSendHttpWebRequest != null)
                {
                    BeforeSendHttpWebRequest(this, new PreSendHttpWebRequestEventArgs(request));
                }

                response = (HttpWebResponse)request.GetResponse();

                // Parse the parameters and re-throw any OAuthRequestException from the service provider
                responseParameters = OAuthParameters.Parse(response);
                OAuthRequestException.TryRethrow(responseParameters);
            }
            catch (WebException e)
            {
                // Parse the parameters and re-throw any OAuthRequestException from the service provider
                responseParameters = OAuthParameters.Parse(e.Response as HttpWebResponse);
                OAuthRequestException.TryRethrow(responseParameters);

                // If no OAuthRequestException, rethrow the WebException
                throw;
            }

            // Store the access token
            AccessToken = new OAuthToken(
                TokenType.Access,
                responseParameters.Token,
                responseParameters.TokenSecret,
                Service.Consumer);

            // Fire the OnReceiveAccessToken event
            var responseArgs = new AccessTokenReceivedEventArgs(
                RequestToken, AccessToken,
                responseParameters.AdditionalParameters);

            if (AccessTokenReceived != null)
            {
                AccessTokenReceived(this, responseArgs);
            }

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public HttpWebRequest CreateAccessTokenRequest()
        {
            // Fire the OnBeforeGetAccessToken event
            var preArgs = new PreAccessTokenRequestEventArgs(
                Service.AccessTokenUrl, Service.AccessTokenEndPoint.HttpMethod,
                RequestToken, RequestTokenVerifier);

            if (BeforeGetAccessToken != null)
            {
                BeforeGetAccessToken(this, preArgs);
            }

            // Create and sign the request
            var authParams = CreateOAuthParameters(null);
            authParams.Verifier = preArgs.Verifier;

            SignParameters(preArgs.RequestUri, preArgs.HttpMethod, authParams, RequestToken);

            return CreateRequest(
                preArgs.RequestUri, authParams, preArgs.HttpMethod,
                string.Compare(preArgs.HttpMethod, "POST", false) == 0 ?
                Constants.HttpPostUrlEncodedContentType : String.Empty,
                null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="requestUri"></param>
        /// <param name="httpMethod"></param>
        /// <param name="authParameters"></param>
        /// <param name="token"></param>
        protected virtual void SignParameters(Uri requestUri, string httpMethod,
            OAuthParameters authParameters, IToken token)
        {
            // Check there is a signing provider for the signature method
            var signingProvider = Service.ComponentLocator.GetInstance<ISigningProvider>(
                            Constants.SigningProviderIdPrefix + Service.SignatureMethod);

            if (signingProvider == null)
            {
                // There is no signing provider for this signature method
                OAuthRequestException.ThrowSignatureMethodRejected(null);
            }

            // Double check the signing provider declares that it can handle the signature method
            if (!signingProvider.SignatureMethod.Equals(Service.SignatureMethod))
                OAuthRequestException.ThrowSignatureMethodRejected(null);

            // Compute the signature
            authParameters.Sign(requestUri, httpMethod, Service.Consumer, token, signingProvider);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="requestUri"></param>
        /// <param name="authParameters"></param>
        /// <param name="httpMethod"></param>
        /// <param name="contentType"></param>
        /// <param name="bodyStream"></param>
        /// <returns></returns>
        protected virtual HttpWebRequest CreateRequest(Uri requestUri, OAuthParameters authParameters,
            string httpMethod, string contentType, Stream bodyStream)
        {
            var postData = string.Empty;
            return CreateRequest(requestUri, authParameters,
                httpMethod, contentType, bodyStream, out postData);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="requestUri"></param>
        /// <param name="authParameters"></param>
        /// <param name="httpMethod"></param>
        /// <param name="contentType"></param>
        /// <param name="bodyStream"></param>
        /// <returns></returns>
        protected virtual HttpWebRequest CreateRequest(Uri requestUri, OAuthParameters authParameters,
            string httpMethod, string contentType, Stream bodyStream, out string postData)
        {
            var requestSpecificParameters = new NameValueCollection(authParameters.AdditionalParameters);
            if (!Service.UseAuthorizationHeader)
            {
                // The OAuth params need to be added either into the query string or into the post body.
                requestSpecificParameters.Add(authParameters.OAuthRequestParams());
            }

            postData = string.Empty;
            if (Constants.HttpPostUrlEncodedContentTypeRegex.IsMatch(contentType) && bodyStream == null)
            {
                // all the requestSpecificParameters need to be encoded into the body bytes
                string body = Rfc3986.EncodeAndJoin(requestSpecificParameters);
                bodyStream = new MemoryStream(Encoding.ASCII.GetBytes(body));
                postData = body;
            }
            else
            {
                // they go into the query string.
                string query = Rfc3986.EncodeAndJoin(requestSpecificParameters);
                if (!string.IsNullOrEmpty(query))
                {
                    var uriBuilder = new UriBuilder(requestUri);
                    uriBuilder.Query = string.IsNullOrEmpty(uriBuilder.Query) ? query :
                        string.Format("{0}&{1}", uriBuilder.Query.Substring(1), query);

                    requestUri = uriBuilder.Uri;
                }
            }

            var request = (HttpWebRequest)HttpWebRequest.Create(requestUri);
            request.Method = httpMethod;

            if (Service.UseAuthorizationHeader)
            {
                request.Headers.Add(HttpRequestHeader.Authorization, authParameters.ToHeaderFormat());
            }

            if (string.IsNullOrEmpty(contentType))
            {
                return request;
            }

            request.ContentType = contentType;

            if (bodyStream == null)
            {
                return request;
            }

            if (bodyStream.CanSeek)
            {
                request.ContentLength = bodyStream.Length;
            }

            StreamCopier.CopyTo(bodyStream, request.GetRequestStream());

            return request;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public bool Authorize()
        {
            if (AccessToken == null && RequestToken == null)
            {
                DoGetRequestToken();  // have not request token so get one
                if (RequestToken == null)
                {
                    throw new InvalidOperationException("Request token was not received.");
                }

                // Get the authorization handler to authorize the request token
                // Halt processing if the authorization handler is out-of-band
                if (!DoAuthorizeRequestToken())
                {
                    return false;
                }

                if (string.IsNullOrEmpty(RequestTokenVerifier))
                {
                    // try to collect the verifier
                    DoCollectVerifier();
                }

                if (string.IsNullOrEmpty(RequestTokenVerifier))
                {
                    return false;
                }

                return false;
            }

            return true;
        }

        /// <param name="parameters">Additional parameters to send with the protected resource request</param>
        /// <exception cref="OAuth.Net.Common.OAuthRequestException">
        /// <list>
        /// <item>If the server responds with an OAuthRequestException</item>
        /// <item>If the server's responds unexpectedly</item>
        /// <item>If the requests to the server cannot be signed</item>
        /// </list>
        /// </exception>
        public OAuthResponse GetResource(NameValueCollection parameters = null)
        {
            return GetResource(parameters,
                string.Compare(ResourceEndPoint.HttpMethod, "POST", false) == 0 ||
                string.Compare(ResourceEndPoint.HttpMethod, "PUT", false) == 0 ?
                Constants.HttpPostUrlEncodedContentType : String.Empty,
                null);
        }

        /// <param name="contentType">The HTTP content-type of the entity contained in the body</param>
        /// <param name="bodyStream">The stream of bytes to send in the request to the resource</param>
        /// <exception cref="OAuth.Net.Common.OAuthRequestException">
        /// <list>
        /// <item>If the server responds with an OAuthRequestException</item>
        /// <item>If the server's responds unexpectedly</item>
        /// <item>If the requests to the server cannot be signed</item>
        /// </list>
        /// </exception>
        public OAuthResponse GetResource(string contentType, Stream bodyStream)
        {
            //The contentType must be supplied but bodyBytes can be null or 0 length.
            if (String.IsNullOrEmpty(contentType))
            {
                throw new ArgumentException("Cannot be null or empty", "contentType");
            }

            if (Constants.HttpPostUrlEncodedContentTypeRegex.IsMatch(contentType))
            {
                throw new ArgumentException(
                    String.Format(
                        "Invalid method call.  Use GetResource(NameValueCollection parameters) for HTTP requests of content-type {0}.",
                        Constants.HttpPostUrlEncodedContentType),
                    "contentType");
            }

            ////Check to see if we are a GET or DELETE can't send a body in a GET or DELETE
            if (string.Compare(ResourceEndPoint.HttpMethod, "GET", false) == 0 ||
                string.Compare(ResourceEndPoint.HttpMethod, "DELETE", false) == 0)
            {
                throw new InvalidOperationException(
                    "You cannot send an entity in the HTTP request in a GET or DELETE HttpMethod.");
            }

            return GetResource(null, contentType, bodyStream);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="contentType"></param>
        /// <param name="bodyStream"></param>
        /// <returns></returns>
        private OAuthResponse GetResource(NameValueCollection parameters,
            string contentType, Stream bodyStream)
        {
            OAuthResponse response;

            var postData = string.Empty;
            var httpRequest = PrepareProtectedResourceRequest(parameters,
                contentType, bodyStream, out postData);

            // A null value for the HttpWebRequest is returned when a ResponseToken is returned
            // and no one has returned in the AuthorizationHandler continue with getting an AccessToken
            // or an RequestToken exists but the AccessToken request was refused.
            if (httpRequest == null)
            {
                response = new OAuthResponse(RequestToken);
            }
            else
            {
                OAuthResource resource;
                OAuthParameters responseParameters;

                try
                {
                    if (BeforeSendHttpWebRequest != null)
                    {
                        BeforeSendHttpWebRequest(this, new PreSendHttpWebRequestEventArgs(httpRequest, postData));
                    }

                    resource = new OAuthResource((HttpWebResponse)httpRequest.GetResponse());

                    // Parse the parameters and re-throw any OAuthRequestException from the service provider
                    responseParameters = OAuthParameters.Parse(resource);
                    OAuthRequestException.TryRethrow(responseParameters);

                    // If nothing is thrown then we should have a valid resource.
                    response = new OAuthResponse(AccessToken ?? RequestToken, resource);
                }
                catch (WebException e)
                {
                    // Parse the parameters and re-throw any OAuthRequestException from the service provider
                    responseParameters = OAuthParameters.Parse(e.Response as HttpWebResponse);
                    OAuthRequestException.TryRethrow(responseParameters);

                    // If no OAuthRequestException, rethrow the WebException
                    //#warning TODO: We have consumer the WebException's body so rethrowing it is pretty pointless; 
                    //#warning TODO: wrap the WebException in an OAuthProtocolException and store the body (create an OAuthResource before parsing parameters)
                    throw;
                }
            }

            return response;
        }

        /// <param name="parameters">Additional parameters to send with the protected resource request</param>
        /// <exception cref="OAuth.Net.Common.OAuthRequestException">
        /// <list>
        /// <item>If the server responds with an OAuthRequestException</item>
        /// <item>If the server's responds unexpectedly</item>
        /// <item>If the requests to the server cannot be signed</item>
        /// </list>
        /// </exception>
        public OAuthResponse GetPublicResource(NameValueCollection parameters = null)
        {
            return GetPublicResource(parameters,
                string.Compare(ResourceEndPoint.HttpMethod, "POST", false) == 0 ||
                string.Compare(ResourceEndPoint.HttpMethod, "PUT", false) == 0 ?
                Constants.HttpPostUrlEncodedContentType : String.Empty,
                null);
        }

        /// <param name="contentType">The HTTP content-type of the entity contained in the body</param>
        /// <param name="bodyStream">The stream of bytes to send in the request to the resource</param>
        /// <exception cref="OAuth.Net.Common.OAuthRequestException">
        /// <list>
        /// <item>If the server responds with an OAuthRequestException</item>
        /// <item>If the server's responds unexpectedly</item>
        /// <item>If the requests to the server cannot be signed</item>
        /// </list>
        /// </exception>
        public OAuthResponse GetPublicResource(string contentType, Stream bodyStream)
        {
            //The contentType must be supplied but bodyBytes can be null or 0 length.
            if (String.IsNullOrEmpty(contentType))
            {
                throw new ArgumentException("Cannot be null or empty", "contentType");
            }

            if (Constants.HttpPostUrlEncodedContentTypeRegex.IsMatch(contentType))
            {
                throw new ArgumentException(
                    String.Format(
                        "Invalid method call.  Use GetResource(NameValueCollection parameters) for HTTP requests of content-type {0}.",
                        Constants.HttpPostUrlEncodedContentType),
                    "contentType");
            }

            ////Check to see if we are a GET or DELETE can't send a body in a GET or DELETE
            if (string.Compare(ResourceEndPoint.HttpMethod, "GET", false) == 0 ||
                string.Compare(ResourceEndPoint.HttpMethod, "DELETE", false) == 0)
            {
                throw new InvalidOperationException(
                    "You cannot send an entity in the HTTP request in a GET or DELETE HttpMethod.");
            }

            return GetPublicResource(null, contentType, bodyStream);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="contentType"></param>
        /// <param name="bodyStream"></param>
        /// <returns></returns>
        private OAuthResponse GetPublicResource(NameValueCollection parameters,
            string contentType, Stream bodyStream)
        {
            OAuthResponse response;

            var postData = string.Empty;
            var request = PreparePublicResourceRequest(parameters,
                contentType, bodyStream, out postData);

            // A null value for the HttpWebRequest is returned when a ResponseToken is returned
            // and no one has returned in the AuthorizationHandler continue with getting an AccessToken
            // or an RequestToken exists but the AccessToken request was refused.
            if (request == null)
            {
                response = new OAuthResponse(RequestToken);
            }
            else
            {
                OAuthResource oauthResource;
                OAuthParameters responseParameters;

                try
                {
                    if (BeforeSendHttpWebRequest != null)
                    {
                        BeforeSendHttpWebRequest(this, new PreSendHttpWebRequestEventArgs(request, postData));
                    }

                    oauthResource = new OAuthResource((HttpWebResponse)request.GetResponse());

                    // Parse the parameters and re-throw any OAuthRequestException from the service provider
                    responseParameters = OAuthParameters.Parse(oauthResource);
                    OAuthRequestException.TryRethrow(responseParameters);

                    // If nothing is thrown then we should have a valid resource.
                    response = new OAuthResponse(AccessToken ?? RequestToken, oauthResource);
                }
                catch (WebException e)
                {
                    // Parse the parameters and re-throw any OAuthRequestException from the service provider
                    responseParameters = OAuthParameters.Parse(e.Response as HttpWebResponse);
                    OAuthRequestException.TryRethrow(responseParameters);

                    // If no OAuthRequestException, rethrow the WebException
                    //#warning TODO: We have consumer the WebException's body so rethrowing it is pretty pointless; 
                    //#warning TODO: wrap the WebException in an OAuthProtocolException and store the body (create an OAuthResource before parsing parameters)
                    throw;
                }
            }

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="additionalParameters"></param>
        /// <returns></returns>
        private OAuthParameters CreateOAuthParameters(NameValueCollection additionalParameters)
        {
            int timestamp = UnixTime.ToUnixTime(DateTime.Now);

            var authParameters = new OAuthParameters()
            {
                ConsumerKey = Service.Consumer.Key,
                Realm = Service.Realm,
                SignatureMethod = Service.SignatureMethod,
                Timestamp = timestamp.ToString(CultureInfo.InvariantCulture),
                Nonce = Service.ComponentLocator.GetInstance<INonceProvider>().GenerateNonce(timestamp),
                Version = Service.OAuthVersion
            };

            if (additionalParameters != null && additionalParameters.Count > 0)
                authParameters.AdditionalParameters.Add(additionalParameters);

            return authParameters;
        }

        /// <summary>
        /// 
        /// </summary>
        private class StatelessRequestStateStore : IRequestStateStore
        {
            /// <summary>
            /// 
            /// </summary>
            /// <param name="state"></param>
            public void Store(RequestState state)
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="key"></param>
            /// <returns></returns>
            public RequestState Get(RequestStateKey key)
            {
                throw new NotImplementedException();
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="key"></param>
            public void Delete(RequestStateKey key)
            {
                throw new NotImplementedException();
            }
        }
    }
}
